/*
 * Copyright 2007 Steven Blundy.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.caree


import scala.{Option, Some, None}
import scala.actors.OutputChannel
import scala.xml.{Node, Document, Elem}
import scala.xml.parsing.ConstructingParser
import scala.io.Source

import java.net.{URL, HttpURLConnection}
import java.io.PushbackInputStream

import org.apache.log4j.Logger

/** 
 * Handles the protocal specific transmittion of an XML payload
 */
trait XmlTransmitter {
  /**
   * Sends an XML node to some endpoint
   * @return The XML response
   */
  @throws(classOf[SoapException])    
  def transmit(msg: Document): Document
}

/**
 * Transmits over http
 */
trait HttpXmlTransmitter extends XmlTransmitter {
  private val logger = Logger.getLogger(classOf[HttpXmlTransmitter])
  private val ptr = new scala.xml.PrettyPrinter(scala.Math.MAX_INT, 2)
    
  protected def url: URL
  
  def transmit(msg: Document): Document = {
    val conn = url.openConnection().asInstanceOf[HttpURLConnection]

    conn.setRequestMethod("POST")
    conn.setDoOutput(true)
    conn.setUseCaches(false);
    conn.setRequestProperty("Accept", "application/soap+xml, application/dime, multipart/related, text/*")
    conn.setRequestProperty("SOAPAction", "\"\"")

    conn.connect()

    return try {
      logger.debug("connected")

      val out = new java.io.OutputStreamWriter(conn.getOutputStream())
      if(logger.isDebugEnabled()) { logger.debug("pretty=" + ptr.format(msg.docElem)) }
      //TODO: switch to a more efficient writer based approach
      out.write(ptr.format(msg.docElem))
      out.write("\n")
      out.flush()
      val in = new PushbackInputStream(if(conn.getResponseCode() == HttpURLConnection.HTTP_OK) conn.getInputStream() else conn.getErrorStream())

      if(findStart(in)) {
        val cpa = ConstructingParser.fromSource(Source.fromInputStream(in), false).document()
        if(null == cpa) {throw new SoapException("Reply not parsable") } else cpa
      } else {
        //TODO: error condition
        logger.warn("conn.responseCode=" + conn.getResponseCode() + ",responseMessage=" + conn.getResponseMessage())
        throw new SoapException("Reply contains no data")
      }
    } finally {
      conn.disconnect()
      logger.debug("disconnected")
    }
  }

  private def findStart(in:PushbackInputStream): Boolean = {
    val next = in.read()
    return if(-1 == next) { false }
    else if(!scala.xml.Utility.isSpace(next.asInstanceOf[Char])) {
      in.unread(next)
      true
    } else findStart(in)
  }
}